/**
 * 
 */
package components.functionUnits;

import components.Util;
import components.exceptions.BinaryToIntException;
import components.exceptions.IntToBinaryException;
import components.memory.Memory;
import components.register.RegisterGroup;

/**
 * @author Chz
 * 
 */
public class ALU {

	private static final int OPLEN = Memory.WORDLENGTH;

	private ALU() {

	}

	/***
	 * Add two binary arrays and return the result. Two params can be binary
	 * arrays with any length, even not in same length.
	 * 
	 * @param op1
	 * @param op2
	 * @return
	 */
	// tested
	public static int[] add(int[] op1, int[] op2) {
		int len = OPLEN;
		int[] res = new int[len];
		try {
			// to check whether overflow or not, first calculate the result
			// in decimal; then calculate the binaried result.
			// if the two results are different with other, then throw
			// data overflow exception
			int op1Int = Util.binaryToInt(op1);
			int op2Int = Util.binaryToInt(op2);

			int originalResult = op1Int + op2Int;

			// if overflow happens, then we must not set the sigbit option,
			// otherwise it would change the result.
			res = Util.intToBinary(originalResult, len, false);

			// check overflow or not by check the sigbit and the originalResult
			if ((res[0] == 0 && originalResult < 0)
					|| (res[0] == 1 && originalResult >= 0)) {
				RegisterGroup.getInstance().setCC(RegisterGroup.CC_OVERFLOW, 1);
				// even if overflow happens, we should not change the result.
				// res = Util.intToBinary(originalResult, len, true);
			}

		} catch (BinaryToIntException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IntToBinaryException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return res;
	}

	/***
	 * do the operation: res = op1 - op2 and return the result.
	 * 
	 * @param op1
	 * @param op2
	 * @return
	 */
	// TODO: underflow not check
	public static int[] sub(int[] op1, int[] op2) {
		int len = OPLEN;
		int[] res = new int[len];
		try {
			int op1Int = Util.binaryToInt(op1);
			int op2Int = Util.binaryToInt(op2);

			int originalResult = op1Int - op2Int;

			// if underflow happens, then we must not set the sigbit option,
			// otherwise it would change the result.
			res = Util.intToBinary(originalResult, len, false);

			// check underflow by check the sigbit and the originalResult
			if ((res[0] == 0 && originalResult < 0)
					|| (res[0] == 1 && originalResult >= 0)) {
				RegisterGroup.getInstance()
						.setCC(RegisterGroup.CC_UNDERFLOW, 1);
				// even if overflow happens, we should not change the result.
				// res = Util.intToBinary(originalResult, len, true);
			}

		} catch (IntToBinaryException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BinaryToIntException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return res;
	}

	/***
	 * mul op1 and op2 into int[] res, then return the res array. the length of
	 * res array is either 16, 32, 64 or 128.
	 * 
	 * In the res array, first half part contains the high order bits, second
	 * half part contains low order bits.
	 * 
	 * @param op1
	 * @param op2
	 * @return
	 */
	// TODO: overflow not tested
	public static int[] mul(int[] op1, int[] op2) {
		int op1Int = 0, op2Int = 0;
		int[] res = null;
		try {
			op1Int = Util.binaryToInt(op1, true);
			op2Int = Util.binaryToInt(op2, true);
		} catch (BinaryToIntException e) {
			e.printStackTrace();
			System.err.println("fatal error: Fail in ALU.MUL!");
		}
		int resInt = op1Int * op2Int;
		int resLen = op1.length + op2.length;
		if (resLen <= 16)
			resLen = 16;
		else if (resLen <= 32)
			resLen = 32;
		else if (resLen <= 64)
			resLen = 64;
		else
			resLen = 128;

		try {
			res = Util.intToBinary(resInt, resLen, false);

			if ((res[0] == 0 && resInt < 0) || (res[0] == 1 && resInt >= 0)) {
				RegisterGroup.getInstance().setCC(RegisterGroup.CC_OVERFLOW, 1);
			}
		} catch (IntToBinaryException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return res;
	}

	/***
	 * divide op1 by op2, means res = op1/op2. res is an array, with first half
	 * contains the quotient and the second half contains the remainder. the
	 * length of res array is either 16, 32, 64 or 128.
	 * 
	 * @param op1
	 * @param op2
	 * @return
	 */
	// tested
	public static int[] div(int[] op1, int[] op2) {
		int op1Int = 0, op2Int = 1;
		try {
			op1Int = Util.binaryToInt(op1, true);
			op2Int = Util.binaryToInt(op2, true);
			if (op2Int == 0) {
				RegisterGroup.getInstance().setCC(
						RegisterGroup.CC_DIVISIONALBY0, 1);
				// TODO:
				return null;
			}
		} catch (BinaryToIntException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		int quotient = op1Int / op2Int;
		int remainder = op1Int % op2Int;

		int len = Util.trimLength(Math.max(op1.length, op2.length));
		// now, the len can fit with both quotient and remainder in binary
		// length.

		int[] quitientArray = {};
		int[] remainderArray = {};
		try {
			quitientArray = Util.intToBinary(quotient, len);
			remainderArray = Util.intToBinary(remainder, len);
		} catch (IntToBinaryException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// combine the two arrays together.
		int[] resArray = new int[len * 2];
		for (int i = 0; i < len; i++) {
			resArray[i] = quitientArray[i];
			resArray[len + i] = remainderArray[i];
		}// this for loop seems has bad locality!
		return resArray;
	}

	/***
	 * test the equality of op1 and op2; if equals, return 1; else return 0
	 * 
	 * @param op1
	 * @param op2
	 * @return 0 if not equals, 1 if equals
	 */
	// tested
	public static int tst(int[] op1, int[] op2) {

		// consider the following situations: 00001 == 01
		// the the first step is to call Util.trimArray to trim the op1 and op2,
		// make both 00001 and 01 into 1;

		int[] trimedOp1 = Util.trimArray(op1);
		int[] trimedOp2 = Util.trimArray(op2);

		// then if the trimed length are not equal, the two arrays are not equal
		if (trimedOp1.length != trimedOp2.length)
			return 0;

		// at last, make a for-loop to test it.
		for (int i = 0; i < trimedOp1.length; ++i) {
			if (trimedOp1[i] != trimedOp2[i])
				return 0;
		}

		return 1;
	}

	/***
	 * do binary AND operation
	 * 
	 * @param op1
	 * @param op2
	 * @return op1 AND op2
	 */
	// TODO: to be tested.
	public static int[] and(int[] op1, int[] op2) {

		// first, make a for-loop to do AND operation from right to left;
		int cnt = 0;
		int i = 0;
		// this two array is to prevent the two input arrays have different
		// length
		int[] op1OpArray = new int[OPLEN];
		int[] op2OpArray = new int[OPLEN];

		// fill up op1's op array:
		for (i = 0; i < op1.length; i++) {
			op1OpArray[OPLEN - i - 1] = op1[op1.length - i - 1];
		}
		for (; i < OPLEN - 1; i++)
			op1OpArray[OPLEN - i - 1] = 0;

		// fill up op2's op array:
		for (i = 0; i < op2.length; i++) {
			op2OpArray[OPLEN - i - 1] = op2[op2.length - i - 1];
		}
		for (; i < OPLEN - 1; i++)
			op2OpArray[OPLEN - i - 1] = 0;

		// direct fill the res array.
		int[] res = new int[OPLEN];

		for (cnt = 0; cnt < OPLEN; cnt++) {
			res[OPLEN - cnt - 1] = op1OpArray[OPLEN - cnt - 1]
					& op2OpArray[OPLEN - cnt - 1];

		}

		return res;
	}

	/***
	 * do binary OR operation
	 * 
	 * @param op1
	 * @param op2
	 * @return op1 OR op2
	 */
	// TODO: to be tested
	public static int[] or(int[] op1, int[] op2) {

		// first, make a for-loop to do AND operation from right to left;
		int cnt = 0;
		int i = 0;
		// this two array is to prevent the two input arrays have different
		// length
		int[] op1OpArray = new int[OPLEN];
		int[] op2OpArray = new int[OPLEN];

		// fill up op1's op array:
		for (i = 0; i < op1.length; i++) {
			op1OpArray[OPLEN - i - 1] = op1[op1.length - i - 1];
		}
		for (; i < OPLEN - 1; i++)
			op1OpArray[OPLEN - i - 1] = 0;

		// fill up op2's op array:
		for (i = 0; i < op2.length; i++) {
			op2OpArray[OPLEN - i - 1] = op2[op2.length - i - 1];
		}
		for (; i < OPLEN - 1; i++)
			op2OpArray[OPLEN - i - 1] = 0;

		// direct fill the res array.
		int[] res = new int[OPLEN];

		for (cnt = 0; cnt < OPLEN; cnt++) {
			res[OPLEN - cnt - 1] = op1OpArray[OPLEN - cnt - 1]
					| op2OpArray[OPLEN - cnt - 1];

		}

		return res;
	}

	/***
	 * do binary NOT operation
	 * 
	 * @param op1
	 * @return NOT op1
	 */
	// TODO: to be tested
	public static int[] not(int[] op1) {

		int[] res = new int[op1.length];

		for (int i = 0; i < op1.length; i++)
			res[i] = 1 - op1[i];

		return res;
	}

	/****
	 * 
	 * SRC: Shift Register by Count. Shift instructions manipulate a 16-bit
	 * datum in a register. In Algorithm shift, the sign bit is keep unchanged;
	 * while in Logical shift, the sign bit is rotating with other bits.
	 * 
	 * @param op
	 *            , is the target array to be shifted
	 * @param lr
	 *            , Left or Right bit, 0 means shifte right, 1 means shift left.
	 * @param al
	 *            , Arithmetic or Logical bit, 0 means Arithmetic shift, 1 means
	 *            Logical shift.
	 * @param count
	 *            , number of bits to be shifted.
	 * @return shifted op
	 */
	// TODO: to be tested
	public static int[] src(int[] op, int lr, int al, int count) {
		int len = op.length;
		int[] res = new int[len];
		if (lr == 0) {
			// shift right
			if (al == 0) {
				// right arithmetic shift
				// shift count times, each time 1 bit shifts.
				for (int i = 0; i < count; i++) {
					// keep the last bit
					// the 0th bit remains unchanged, shift
					// from the 1st to the last.
					for (int j = len - 1; j > 1; j--) {
						res[j - 1] = op[j];
					}
					res[1] = 0;
				}
			} else {
				// right logical shift
				// shift count times, each time 1 bit shifts.
				for (int i = 0; i < count; i++) {
					// keep the last bit
					// the 0th bit remains unchanged, shift
					// from the 1st to the last.
					for (int j = len - 1; j > 0; j--) {
						res[j - 1] = op[j];
					}
					res[0] = 0;
				}
			}

		} else {
			// shift left
			if (al == 0) {
				// left arithmetic shift
				// shift count times, each time 1 bit shifts.
				for (int i = 0; i < count; i++) {
					// keep the last bit
					// the 0th bit remains unchanged, shift
					// from the 1st to the last.
					for (int j = 1; j < len - 1; j++) {
						res[j] = op[j + 1];
					}
					res[len - 1] = 0;
				}
			} else {
				// left logical shift
				// shift count times, each time 1 bit shifts.
				for (int i = 0; i < count; i++) {
					// keep the last bit
					// the 0th bit remains unchanged, shift
					// from the 1st to the last.
					for (int j = 0; j < len - 1; j++) {
						res[j] = op[j + 1];
					}
					res[len - 1] = 0;
				}
			}
		}

		return res;
	}

	/****
	 * 
	 * RRC: Rotate Register by Count. Rotate instructions manipulate a 16-bit
	 * datum in a register. In Algorithm Rotate, the sign bit is keep unchanged;
	 * while in Logical rotate, the sign bit is rotating with other bits.
	 * 
	 * @param op
	 *            , is the target array to be rotated
	 * @param lr
	 *            , Left or Right bit, 0 means rotate right, 1 means rotate
	 *            left.
	 * @param al
	 *            , Arithmetic or Logical bit, 0 means Arithmetic rotate, 1
	 *            means Logical rotate.
	 * @param count
	 *            , number of bits to be rotated.
	 * @return rotated op
	 */
	// TODO: to be tested
	public static int[] rrc(int[] op, int lr, int al, int count) {

		int tmp = 0;
		int len = op.length;
		int[] res = new int[len];
		if (lr == 0) {
			// rotate right
			if (al == 0) {
				// right arithmetic rotate
				// rotate count times, each time 1 bit shifts.
				for (int i = 0; i < count; i++) {
					// keep the last bit
					tmp = op[len - 1];
					// the 0th bit remains unchanged, rotate
					// from the 1st to the last.
					for (int j = len - 1; j > 1; j--) {
						res[j - 1] = op[j];
					}
					res[1] = tmp;
				}
			} else {
				// right logical rotate
				// rotate count times, each time 1 bit shifts.
				for (int i = 0; i < count; i++) {
					// keep the last bit
					tmp = op[len - 1];
					// the 0th bit remains unchanged, rotate
					// from the 1st to the last.
					for (int j = len - 1; j > 0; j--) {
						res[j - 1] = op[j];
					}
					res[0] = tmp;
				}
			}

		} else {
			// rotate left
			if (al == 0) {
				// left arithmetic rotate
				// rotate count times, each time 1 bit shifts.
				for (int i = 0; i < count; i++) {
					// keep the last bit
					tmp = op[1];
					// the 0th bit remains unchanged, rotate
					// from the 1st to the last.
					for (int j = 1; j < len - 1; j++) {
						res[j] = op[j + 1];
					}
					res[len - 1] = tmp;
				}
			} else {
				// left logical rotate
				// rotate count times, each time 1 bit shifts.
				for (int i = 0; i < count; i++) {
					// keep the last bit
					tmp = op[0];
					// the 0th bit remains unchanged, rotate
					// from the 1st to the last.
					for (int j = 0; j < len - 1; j++) {
						res[j] = op[j + 1];
					}
					res[len - 1] = tmp;
				}
			}
		}

		return res;
	}

}
